﻿namespace Home.Data
{
    using System;
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Data.Entity;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Transactions;

    public abstract class EntityFrameworkDbContext : DbContext
    {
        private TransactionScope transaction;
        private bool isTransactionCommitted;

        protected EntityFrameworkDbContext(string nameOrConnectionString, bool enableTransaction)
            : base(nameOrConnectionString)
        {
            Initialize(enableTransaction);
        }

        protected EntityFrameworkDbContext(DbConnection existingConnection, bool contextOwnsConnection, bool enableTransaction)
            : base(existingConnection, contextOwnsConnection)
        {
            Initialize(enableTransaction);
        }

        public override int SaveChanges()
        {
            var result = base.SaveChanges();
            EnsureCommitTransaction();
            return result;
        }

        public override async Task<int> SaveChangesAsync()
        {
            var result = await base.SaveChangesAsync();
            EnsureCommitTransaction();
            return result;
        }

        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
        {
            var result = await base.SaveChangesAsync(cancellationToken);
            EnsureCommitTransaction();
            return result;
        }

        private void Initialize(bool enableTransaction)
        {
            if (enableTransaction)
            {
                this.transaction = new TransactionScope(
                    TransactionScopeOption.Required,
                    new TransactionOptions()
                    {
                        IsolationLevel = IsolationLevel.ReadCommitted
                    },
                    TransactionScopeAsyncFlowOption.Enabled);
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing && this.transaction != null)
            {
                this.transaction.Dispose();
                this.transaction = null;
            }
        }

        private void EnsureCommitTransaction()
        {
            if (this.transaction != null && !this.isTransactionCommitted)
            {
                try
                {
                    this.transaction.Complete();
                }
                finally
                {
                    // if commit transaction was failed, we need to disable commit twice
                    this.isTransactionCommitted = true;
                }
            }
        }
    }
}
